/**
 * 拓扑排序函数
 * 拓扑排序是对一个有向无环图进行的排序过程，使得图中所有顶点的先后顺序满足
 * 其与其他顶点的依赖关系。
 * 这种排序可以用于任务调度、编译器优化等领域，其中任务或语句之间存在先后执行的关系
 * @param {Array} nodes - 所有节点列表 (确保所有节点都被显式声明)
 * @param {Array} edges - 边数组，格式为 [[u, v], ...] 表示 u → v 的依赖
 * @return {Array} - 拓扑排序结果（若存在环则返回空数组）
 */
function topologicalSort(nodes, edges) {
  // 初始化邻接表 和 入度表
  const adjList = new Map(); // 邻接表：记录每个节点的后续节点
  const inDegree = new Map(); // 入度表：记录每个节点的入度值
  const queue = []; // 入度为0的节点队列
  const result = []; // 排序结果

  // 初始化所有节点的数据结构
  nodes.forEach(node => {
    adjList.set(node, []);
    inDegree.set(node, 0);
  });

  // 构建邻接表和入度表
  edges.forEach(([u, v]) => {
    adjList.get(u).push(v); // u → v 的边
    inDegree.set(v, inDegree.get(v) + 1); // v的入度+1
  });

  // 初始入度为0的节点入队
  inDegree.forEach((degree, node) => {
    if (degree === 0) queue.push(node);
  });

  // 处理队列中的节点
  while (queue.length) {
    const u = queue.shift(); // 出队一个入度为0的节点
    result.push(u);
    // 遍历u的所有邻接节点v
    adjList.get(u).forEach(v => {
      inDegree.set(v, inDegree.get(v) - 1); // v的入度-1
      if (inDegree.get(v) === 0) {
        queue.push(v); // 若v入度变为0，则入队
      }
    });
  }
  // 检查是否存在环（排序结果数量 ≠ 节点总数）
  return result.length === nodes.length ? result : [];
}

const courses = ['数学', '编程', '算法', '项目', '机器学习'];
const dependencies = [
  ['数学', '编程'], // 学编程需要先学数学
  ['编程', '算法'], // 学算法需要先学编程
  ['算法', '机器学习'], // 学机器学习需要先学算法
  ['编程', '项目'], // 做项目需要先学编程
  ['算法', '项目'] // 做项目需要先学算法
];

const order = topologicalSort(courses, dependencies);
console.log(order);
// 可能的输出（多个合法解之一）：
// ['数学', '编程', '算法', '项目', '机器学习']
